<?php
/*
 * Copyright (c) 2009 Tom Smith (tom@takeontom.com).
 * 
 * Distributed under the terms of the GNU Lesser General Public License.
 * 
 * ---------------------------------------------------------------------------------
 * 
 * This file is part of Seraph PHP Framework.
 *
 * Seraph PHP Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Seraph PHP Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Seraph PHP Framework.  If not, see <http://www.gnu.org/licenses/>.
*/


class Entity	{
	protected $_data = array();
	protected $_tableName = null;
	protected $_modifiedColumns = array();
	
	protected $_primaryKey = null;
	
	/**
	 * Get the DAO for this entity.
	 * 
	 * Is static to allow for global access, and to ensure that
	 * there is no confusion over what the DAO::get() does.
	 * 
	 * @return EntityDAO
	 */
	static public function getDAO()	{
		//print_r (debug_backtrace());
		return new EntityDAO();
	}
	
	
	
	public function __set($key, $value)	{
		$this->_data[$key] = $value;
		$this->_modifiedColumns[$key] = true;
	}
	
	public function __get($key)	{
		if (isset($this->_data[$key]))	{
			return $this->_data[$key];
		}
		return null;
	}
	
	public function __call($method, $args)	{
		$entityType = $this->isRequestForRelatedEntities($method, $args);
		if ($entityType !== false)	{
			return $this->getRelatedEntities($entityType);
		}
		
		$entityType = $this->isRequestForRelatedEntity($method, $args);
		if ($entityType !== false)	{
			return $this->getRelatedEntity($entityType);
		}
		
		throw new Exception('Unknown method name: ' . $method);
	}
	
	/**
	 * Checks whether the supplied method name is an attempt to
	 * get the related entities of a specific type.
	 * 
	 * Will return the requested entity type name.
	 * 
	 * @param $method
	 * @param $args
	 * @return string
	 */
	public function isRequestForRelatedEntities($method, $args)	{
		preg_match('/^getAll_([a-z,_]*)$/', $method, $matches);
		
		if (count($matches) > 1)	{
			return ($matches[1]);
		}
		
		return false;
	}
	
	/**
	 * Checks whether the supplied method name is an attempt to
	 * get a single related etity of a specific type.
	 * 
	 * Will return the requested entity type name.
	 * 
	 * @param $method
	 * @param $args
	 * @return string
	 */
	public function isRequestForRelatedEntity($method, $args)	{
		preg_match('/^get_([a-z,_]*)$/', $method, $matches);
		
		if (count($matches) > 1)	{
			return ($matches[1]);
		}
		return false;
	}
	
	/**
	 * Get related entities from the model.
	 * 
	 * @param $entityType
	 * @return EntityContainer
	 */
	public function getRelatedEntities($entityType)	{
		return Model::get($entityType, $this->getForeignKeyValue());
	}
	
	/**
	 * Get related entity
	 * 
	 * @param $entityType
	 * @return EntityContainer
	 */
	public function getRelatedEntity($entityType)	{
		//generate am array containing the identifying keys for the entity
		$tempEntity = new $entityType;
		$entityKeys = $tempEntity->getPrimaryKey();
		
		$useKeys = array();
		foreach($entityKeys as $aKey)	{
			$fKeyName = $entityType . '_' . $aKey;
			$fKeyValue = $this->$fKeyName;
			if ($fKeyValue !== null)	{
				$useKeys[$aKey] = $fKeyValue;
			}
		}
		
		if (count($useKeys) == 0)	{
			return null;
		}
		
		return Model::get($entityType, $useKeys);
	}
	
	public function getEntityData()	{
		return $this->_data;
	}
	
	/**
	 * Sets this entity's raw data.
	 * 
	 * @param $data
	 */
	public function setEntityData(array $data)	{
		$this->_data = $data;
	}
	
	/**
	 * Get the database connection this entity is stored in.
	 * 
	 * @return DBConnection
	 */
	public function getDatabase()	{
		return Model::getDatabase();
	}
	
	public function save()	{
		if ($this->isPrimaryKeySet())	{
			return $this->update();
		} else {
			return $this->insert();
		}
	}
		
	public function insert()	{
		if (self::getDAO()->insert($this))	{
			$this->resetModifiedColumns();
			return true;
		}
		return false;
	}
	
	/**
	 * Update the specified entity.
	 * 
	 * @return bool
	 */
	public function update()	{
		if (count($this->getModifiedColumns()) == 0)	{
			//nothing to update
			return true;
		}
		if (self::getDAO()->update($this))	{
			$this->resetModifiedColumns();
			return true;
		}
		
		return false;
	}
	
	/**
	 * Deletes the entity from the database.
	 * 
	 * @return bool
	 */
	public function delete()	{
		self::getDAO()->delete($this);
	}
	
	protected function resetModifiedColumns()	{
		$this->_modifiedColumns = array();
	}
	
	/**
	 * 
	 * @return array
	 */
	public function getModifiedColumns()	{
		return $this->_modifiedColumns;
	}
	

	/**
	 * Attempts to get the table name for this entity.
	 * 
	 * Will check the following for the table name:
	 * 		$_tableName property
	 * 		class name of entity
	 * 
	 * 
	 * @return string
	 */
	public function getTableName()	{
		if ($this->_tableName !== null)	{
			return $this->_tableName;
		}
		
		return get_class($this);
	}
	
	/**
	 * Get all the column names defined for this entity.
	 * 
	 * @return array
	 */
	public function getColumnNames()	{
		return array_keys($this->_data);
	}
	
	/**
	 * Returns an array containing the column names of the primary key.
	 * 
	 * @return array
	 */
	public function getPrimaryKey()	{
		if ($this->_primaryKey === null)	{
			return array('id');
		}
		
		return $this->_primaryKey;
	}
	
	/**
	 * Takes an array of column names, and confirms whether or
	 * not the keys match this entity's primary key.
	 * 
	 * @param $keys
	 * @return bool
	 */
	public function doesPrimaryKeyMatch(array $keys)	{
		$keys = array_keys($keys);
		
		//echo 'keys: ' . print_r ($keys,true);
		//echo 'primary key: ' . print_r ($this->getPrimaryKey(),true);
		
		$res = array_diff($this->getPrimaryKey(), $keys);
		
		//echo 'res: ' . print_r ($res, true);
		
		return (count($res) == 0);
	}
	
	
	/**
	 * Sets which columns make up the primary key for this entity.
	 * 
	 * @param $primaryKey
	 */
	public function setPrimaryKey(array $primaryKey)	{
		$this->_primaryKey = $primaryKey;
	}
	
	/**
	 * Returns an array containing the unique key for this entity.
	 * 
	 * @return array
	 */
	public function getPrimaryKeyValue()	{
		$out = array();
		foreach($this->getPrimaryKey() as $key)	{
			$out[$key] = $this->$key;
		}
		return $out;
	}
	
	/**
	 * Whether or not the primary key has been set or not.
	 * 
	 * A required check when doing update and delete operations.
	 * 
	 * @return unknown_type
	 */
	public function isPrimaryKeySet()	{
		foreach($this->getPrimaryKeyValue() as $value)	{
			if ($value === null)	{
				return false;
			}
		}
		return true;
	}
	
	
	/**
	 * Get the expected names of the columns used when this entity's
	 * primary key appears in a foreign table.
	 * 
	 * @return array
	 */
	public function getForeignKeyNames()	{
		$out = array();
		foreach($this->getPrimaryKey() as $pKey)	{
			$out[] = $this->convertColumnNameToForeignColumnName($pKey);
		}
		return $out;
	}

	/**
	 * Returns all the primary key values for this entity, but named as foreign
	 * keys.
	 * 
	 * @return array
	 */
	public function getForeignKeyValue()	{
		$pKeys = $this->getPrimaryKeyValue();
		
		//convert the primary key names to foreign key names
		$out = array();
		foreach($pKeys as $key => $value)	{
			$out[$this->convertColumnNameToForeignColumnName($key)] = $value;
		}
		return $out;
	}
	
	/**
	 * Will return the name of this column when used in foreign tables.
	 * 
	 * Most likely this will only be used for primary keys.
	 * 
	 * Allows for a standard foreign key naming convention to be implemented.
	 * 
	 * @param $colName
	 * @return string
	 */
	public function convertColumnNameToForeignColumnName($colName)	{
		return get_class($this) . '_' . $colName;
	}

	
	/**
	 * Used to set the ID columns of the entity after an insert.
	 * 
	 * Requires the last insert id from the database.
	 * 
	 * @param $lastInsertID
	 */
	public function setInsertID($lastInsertID)	{
		$primaryKey = $this->getPrimaryKey();
		
		//check whether primary key needs to be set - for m:n tables will probably not
		//need to use the insert id
		if (!$this->isPrimaryKeySet())	{
			if (count($primaryKey) == 1)	{
				$this->{$primaryKey[0]} = $lastInsertID;
			} else {
				//throw a silly exception
				//TODO
				throw new RuntimeException('Not sure what to do with compound key and insert ID');
			}
		}
	}
}
?>